/**
 * @file phonebook.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_watch.h"

#if USE_LV_WATCH_PHONEBOOK != 0

#include <stdio.h>

/*********************
 *      DEFINES
 *********************/
#define PHONEBOOK_TEST   0
#define PHONEBOOK_DEBUG  1

/* 1: use LV_WATCH_LEFT_OR_RIGHT_SLIDE_CALLBACK. If current user is the first one, right slide, phonebook will be closed.
 * 0: use LV_WATCH_ANIM_VER_UP_HIDE. If ver up slide, phonebook will be closed.
 */
#define PHONEBOOK_USE_RIGHT_SLIDE_HIDE  0

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static nv_watch_phonebook_t * phonebook_read_nvm(void);
static void phonebook_write_nvm(nv_watch_phonebook_t * nvm);
static void phonebook_copy_contact(nv_watch_phonebook_info_t * dest, app_adaptor_contact_t * src);
static void phonebook_update_ui(app_adaptor_contact_t * contact_list, uint8_t count, uint8_t standby_admin_count, uint8_t others_count);
static char * phonebook_get_img_src(uint8_t portrait_id, uint8_t small_flg);
static lv_obj_t * phonebook_create(lv_obj_t * activity_obj);

/**********************
 *  STATIC VARIABLES
 **********************/
static lv_signal_cb_t ancient_page_scrl_signal;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
***********************/
void phonebook_init(void)
{
#if PHONEBOOK_DEBUG != 0
    nv_watch_phonebook_t * nvm = phonebook_read_nvm();
    if(NULL == nvm) return;

    for(uint8_t i = 0; i < NV_WATCH_MAX_CONTACT_NUM; i++) {
        if('\0' == nvm->info[i].number[0]) {
            printf("%s: contact count %d\n", __FUNCTION__, i);
            break;
        }

        printf("%s: number %s, type %d, portrait id %d\n",
               __FUNCTION__,
               nvm->info[i].number,
               nvm->info[i].contact_type,
               nvm->info[i].portrait_id);
    }

    lv_mem_free(nvm);
#endif
}

void phonebook_create_btn_event_cb(lv_obj_t * btn, lv_event_t e)
{
    (void)btn;
    if(LV_EVENT_CLICKED == e) {
        lv_watch_png_cache_all_free();
        phonebook_create(NULL);
    }
}

void app_adaptor_update_phonebook_ind(app_adaptor_contact_t * contact_list, uint8_t count)
{
    /*contacts consist of one administrator, some standby administrators and others.*/
    /*delete all of contacts if administrator is deleted.*/
    /*only add or modify or delete one contact which is not administrator in an update*/

    if(NULL == contact_list) {
        printf("error, contact_list NULL in app_adaptor_update_phonebook_ind\n");
        return;
    }

    uint8_t type = contact_list[0].contact_type; /*0:administrator, 1:standby administrator, 2: others*/
    printf("contact type %d, count %d in app_adaptor_update_phonebook_ind\n", type, count);

    nv_watch_phonebook_t * nvm_contact_list = phonebook_read_nvm();

    uint8_t admin_count = 0;
    uint8_t standby_admin_count = 0;
    uint8_t others_count = 0;
    for(uint8_t i = 0; i < NV_WATCH_MAX_CONTACT_NUM; i++) {
        if('\0' == nvm_contact_list->info[i].number[0]) {
            break;
        }

        if(0 == nvm_contact_list->info[i].contact_type) {
            admin_count++;
        } else if(1 == nvm_contact_list->info[i].contact_type) {
            standby_admin_count++;
        } else if(2 == nvm_contact_list->info[i].contact_type) {
            others_count++;
        }
    }
    printf("old contact count, admin %d, standby admin %d, others %d\n",
           admin_count, standby_admin_count, others_count);

    uint8_t pos_start = 0;
    uint8_t old_count = admin_count;
    if(1 == type) {
        pos_start = admin_count;
        old_count = standby_admin_count;
    } else if(2 == type) {
        pos_start = admin_count + standby_admin_count;
        old_count = others_count;
    }

    voice_msg_update_contact_t * update = (voice_msg_update_contact_t *)lv_mem_alloc(sizeof(voice_msg_update_contact_t));
    update->standby_admin_count = standby_admin_count;
    update->others_count = others_count;
    update->contact_type = type;
    uint32_t contact_length = sizeof(nv_watch_phonebook_info_t);
    if((old_count + 1) == count) {
        /*add a new contact to the end of the contacts with the same contact type*/
        nv_watch_phonebook_info_t * nvm_contact = &nvm_contact_list->info[pos_start + old_count];
        if((1 == type) && (0 != others_count)) {
            /*add a standby administrator, move others down*/
            memmove(&nvm_contact_list->info[admin_count + count],
                    nvm_contact, /*the first others contacts*/
                    contact_length * others_count);
        }
        phonebook_copy_contact(nvm_contact, &contact_list[count - 1]);

#if USE_LV_WATCH_VOICE_MSG != 0
        update->type = VOICE_MSG_UPDATE_TYPE_ADD;
        update->index = pos_start + old_count;
        update->old_name = NULL;
        uint8_t text_len = strlen(contact_list[count - 1].contact_name) + 1;
        update->new_name = (char *)lv_mem_alloc(text_len);
        memcpy(update->new_name, contact_list[count - 1].contact_name, text_len);
        text_len = strlen(contact_list[count - 1].contact_number) + 1;
        update->number = (char *)lv_mem_alloc(text_len);
        memcpy(update->number, contact_list[count - 1].contact_number, text_len);
        update->portrait_id = contact_list[count - 1].portrait_id;
        if(1 == type) {
            update->standby_admin_count++;
        } else if(2 == type) {
            update->others_count++;
        }
#endif
    } else if((0 < old_count) && (old_count == count)) {
        /*update a contact*/
        uint8_t i = 0;
        for(; i < count; i++) {
            nv_watch_phonebook_info_t * nvm_contact = &nvm_contact_list->info[i + pos_start];
            int32_t name_compare = strcmp(nvm_contact->name, contact_list[i].contact_name);
            int32_t number_compare = strcmp(nvm_contact->number, contact_list[i].contact_number);
            if((0 != name_compare) || (0 != number_compare)) {
#if USE_LV_WATCH_VOICE_MSG != 0
                update->type = VOICE_MSG_UPDATE_TYPE_UPDATE;
                update->index = i + pos_start;
                uint8_t text_len = strlen(nvm_contact->name) + 1;
                update->old_name = (char *)lv_mem_alloc(text_len);
                memcpy(update->old_name, nvm_contact->name, text_len);
                if(0 != name_compare) {
                    text_len = strlen(contact_list[i].contact_name) + 1;
                    update->new_name = (char *)lv_mem_alloc(text_len);
                    memcpy(update->new_name, contact_list[i].contact_name, text_len);
                    update->portrait_id = contact_list[i].portrait_id;
                } else {
                    update->new_name = NULL;
                }
                if(0 != number_compare) {
                    text_len = strlen(nvm_contact->number) + 1;
                    update->number = (char *)lv_mem_alloc(text_len);
                    memcpy(update->number, nvm_contact->number, text_len);
                } else {
                    update->number = NULL;
                }
#endif

                phonebook_copy_contact(nvm_contact, &contact_list[i]);
                break;
            }
        }
        if(count == i) {
            printf("ui error, the modifed contact not found in app_adaptor_update_phonebook_ind\n");
            lv_mem_free(contact_list);
            lv_mem_free(nvm_contact_list);
            return;
        }
    } else if((0 < old_count) && ((old_count - 1) == count)) {
        /*delete a contact*/
        if(0 == type) {
            /*administrator deleted, clear all of contacts*/
            memset(nvm_contact_list, 0, sizeof(nv_watch_phonebook_t));

#if USE_LV_WATCH_VOICE_MSG != 0
            update->type = VOICE_MSG_UPDATE_TYPE_DELETE;
            update->index = 0;
            update->old_name = NULL;
            update->new_name = NULL;
            update->number = NULL;
            update->standby_admin_count = 0;
            update->others_count = 0;
#endif
        } else {
            uint8_t i = 0;
            for(; i < count; i++) {
                nv_watch_phonebook_info_t * nvm_contact = &nvm_contact_list->info[pos_start + i];
                if((0 != strcmp(nvm_contact->name, contact_list[i].contact_name))
                        || (0 != strcmp(nvm_contact->number, contact_list[i].contact_number))) {
                    break;
                }
            }

#if USE_LV_WATCH_VOICE_MSG != 0
            update->type = VOICE_MSG_UPDATE_TYPE_DELETE;
            update->index = pos_start + i;
            uint8_t text_len = strlen(nvm_contact_list->info[update->index].name) + 1;
            update->old_name = (char *)lv_mem_alloc(text_len);
            memcpy(update->old_name, nvm_contact_list->info[update->index].name, text_len);
            update->new_name = NULL;
            text_len = strlen(nvm_contact_list->info[update->index].number) + 1;
            update->number = (char *)lv_mem_alloc(text_len);
            memcpy(update->number, nvm_contact_list->info[update->index].number, text_len);
            if(1 == type) {
                update->standby_admin_count--;
            } else if(2 == type) {
                update->others_count--;
            }
#endif

            /*the contact with index (pos_start + i) is deleted*/
            uint8_t move_count = old_count - (i + 1);
            if((1 == type) && (0 != others_count)) {
                move_count += others_count;
            }
            if(0 < move_count) {
                /*move the contacts behind the deleted contact up*/
                memmove(&nvm_contact_list->info[pos_start + i],
                        &nvm_contact_list->info[pos_start + i + 1],
                        contact_length * move_count);
            }
            /*clear the last contact in list*/
            uint8_t last_index = admin_count + standby_admin_count + others_count - 1;
            memset(&nvm_contact_list->info[last_index], 0, contact_length);
        }
    } else {
        printf("ui error, update error in app_adaptor_update_phonebook_ind\n");
        lv_mem_free(contact_list);
        lv_mem_free(nvm_contact_list);
        return;
    }

    phonebook_write_nvm(nvm_contact_list);

    phonebook_update_ui(contact_list, count, standby_admin_count, others_count);

    lv_mem_free(contact_list);
    lv_mem_free(nvm_contact_list);

    voice_msg_update_contact_from_phonebook_ind(update);
}

phonebook_contact_t * phonebook_get_contact_info(char * number)
{
    printf("%s: number %s\n", __FUNCTION__, number);
    if(NULL == number) {
        printf("error, number is NULL in phonebook_get_contact_info\n");
        return NULL;
    }

    nv_watch_phonebook_t * nv_contact_list = phonebook_read_nvm();

    phonebook_contact_t * contact = NULL;
    uint8_t len = strlen(number);
    for(uint8_t i = 0; i < NV_WATCH_MAX_CONTACT_NUM; i++) {
        if('\0' == nv_contact_list->info[i].number[0])
            break;

        bool ret = false;
        uint8_t len_nv = strlen(nv_contact_list->info[i].number);
        if((11 >= len) && (11 >= len_nv)) {
            if(0 == strcmp(number, nv_contact_list->info[i].number)) {
                ret = true;
            }
        } else if((11 <= len) && (11 <= len_nv)) {
            // ignore prefix
            if(len > len_nv) {
                if(0 == strcmp(&number[len - len_nv], nv_contact_list->info[i].number)) {
                    ret = true;
                }
            } else {
                if(0 == strcmp(number, &nv_contact_list->info[i].number[len_nv - len])) {
                    ret = true;
                }
            }
        }
        if(ret) {
            contact = (phonebook_contact_t *)lv_mem_alloc(sizeof(phonebook_contact_t));
            memcpy(contact->contact_name, nv_contact_list->info[i].name, PHONEBOOK_CONTACTS_MAX_NAME_LEN);
            memcpy(contact->contact_number, number, strlen(number) + 1);
            contact->contact_type = nv_contact_list->info[i].contact_type;
            contact->img_src = phonebook_get_img_src(nv_contact_list->info[i].portrait_id, 0);
            contact->img_src_small = phonebook_get_img_src(nv_contact_list->info[i].portrait_id, 1);
            printf("%s: find contact %s %s\n", __FUNCTION__, contact->contact_name, contact->contact_number);
            break;
        }
    }

    lv_mem_free(number);
    lv_mem_free(nv_contact_list);

    return contact;
}

char * phonebook_get_phone_number(uint8_t id)
{
    printf("%s: index %d\n", __FUNCTION__, id);

    if((NV_WATCH_MAX_CONTACT_NUM - 1) < id) {
        return NULL;
    }

    uint32_t length = sizeof(nv_watch_phonebook_info_t);
    nv_watch_phonebook_info_t * info = (nv_watch_phonebook_info_t *)lv_mem_alloc(length);
    uint32_t offset = id * length;
    if(length != UI_NV_Read_Req(NV_SECTION_UI_PHONEBOOK, offset, length, (uint8_t *)info)) {
        printf("read nvm error in phonebook_get_phone_number\n");
        lv_mem_free(info);
        return NULL;
    }

    uint8_t name_length = strlen(info->number);
    char * number = NULL;
    if(0 != name_length) {
        number = (char *)lv_mem_alloc(name_length + 1);
        memcpy(number, info->number, (name_length + 1));
        printf("%s: number %s in phonebook %d\n", __FUNCTION__, number, id);
    }
    lv_mem_free(info);

    return number;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static uint32_t phonebook_get_user_num(lv_obj_t * obj)
{
    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(obj);
    return user_data->user_num;
}

static nv_watch_phonebook_t * phonebook_read_nvm(void)
{
    uint32_t length = sizeof(nv_watch_phonebook_t);
    nv_watch_phonebook_t * nvm = (nv_watch_phonebook_t *)lv_mem_alloc(length);
    if(length != UI_NV_Read_Req(NV_SECTION_UI_PHONEBOOK, 0, length, (uint8_t *)nvm)) {
        printf("read nvm error in phonebook_read_nvm\n");
        lv_mem_free(nvm);
        return NULL;
    }
    return nvm;
}

static void phonebook_write_nvm(nv_watch_phonebook_t * nvm)
{
    if(NULL == nvm) return;

    uint32_t length = sizeof(nv_watch_phonebook_t);
    if(length != UI_NV_Write_Req(NV_SECTION_UI_PHONEBOOK, 0, length, (uint8_t *)nvm)) {
        printf("write contacts to nvm error in phonebook_write_nvm\n");
        return;
    }
}

static void phonebook_copy_contact(nv_watch_phonebook_info_t * dest, app_adaptor_contact_t * src)
{
    if((NULL == dest) || (NULL == src)) {
        printf("error, dest or src contact is NULL in phonebook_copy_contact\n");
    }
    memcpy(dest->name, src->contact_name, WATCH_CONTACTS_MAX_NAME_LEN);
    memcpy(dest->number, src->contact_number, WATCH_CONTACTS_MAX_NUMBER_LEN);
    dest->contact_type = src->contact_type;
    dest->portrait_id = src->portrait_id;
}

static char * phonebook_get_img_src(uint8_t portrait_id, uint8_t small_flg)
{
    switch(portrait_id) {
        case WATCH_PORTRAIT_ID_FATHER:
            if(small_flg) {
                return (ICON_FATHER_SMALL);
            } else {
                return (ICON_FATHER);
            }
        case WATCH_PORTRAIT_ID_MOTHER:
            if(small_flg) {
                return (ICON_MOTHER_SMALL);
            } else {
                return (ICON_MOTHER);
            }
        case WATCH_PORTRAIT_ID_PATERNAL_GRADFATHER:
            if(small_flg) {
                return (ICON_PATERNAL_GRADFATHER_SMALL);
            } else {
                return (ICON_PATERNAL_GRADFATHER);
            }
        case WATCH_PORTRAIT_ID_PATERNAL_GRADMOTHER:
            if(small_flg) {
                return (ICON_PATERNAL_GRADMOTHER_SMALL);
            } else {
                return (ICON_PATERNAL_GRADMOTHER);
            }
        case WATCH_PORTRAIT_ID_MATERNAL_GRADFATHER:
            if(small_flg) {
                return (ICON_MATERNAL_GRADFATHER_SMALL);
            } else {
                return (ICON_MATERNAL_GRADFATHER);
            }
        case WATCH_PORTRAIT_ID_MATERNAL_GRADMOTHER:
            if(small_flg) {
                return (ICON_MATERNAL_GRADMOTHER_SMALL);
            } else {
                return (ICON_MATERNAL_GRADMOTHER);
            }
        case WATCH_PORTRAIT_ID_UNCLE:
            if(small_flg) {
                return (ICON_UNCLE_SMALL);
            } else {
                return (ICON_UNCLE);
            }
        case WATCH_PORTRAIT_ID_AUNT:
            if(small_flg) {
                return (ICON_AUNT_SMALL);
            } else {
                return (ICON_AUNT);
            }
        case WATCH_PORTRAIT_ID_BROTHER:
            if(small_flg) {
                return (ICON_BROTHER_SMALL);
            } else {
                return (ICON_BROTHER);
            }
        case WATCH_PORTRAIT_ID_BOY:
            if(small_flg) {
                return (ICON_BOY_SMALL);
            } else {
                return (ICON_BOY);
            }
        case WATCH_PORTRAIT_ID_SISTER:
            if(small_flg) {
                return (ICON_SISTER_SMALL);
            } else {
                return (ICON_SISTER);
            }
        case WATCH_PORTRAIT_ID_GIRL:
            if(small_flg) {
                return (ICON_GIRL_SMALL);
            } else {
                return (ICON_GIRL);
            }
        case WATCH_PORTRAIT_ID_FAMILY:
            if(small_flg) {
                return (ICON_FAMILY_SMALL);
            } else {
                return (ICON_FAMILY);
            }
        case WATCH_PORTRAIT_ID_OTHERS:
            if(small_flg) {
                return (ICON_OTHER_SMALL);
            } else {
                return (ICON_OTHER);
            }
        default:
            printf("warning! no icon corresponding to portrait_id %d", portrait_id);
            break;
    }
    return NULL;
}

static lv_phonebook_obj_ext_t * phonebook_get_ext(void)
{
    lv_obj_t * activity_obj = lv_watch_get_activity_obj(ACT_ID_PHONEBOOK);
    if(activity_obj == NULL) return NULL;

    lv_obj_t * watch_obj;
    lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &watch_obj);
    if(watch_obj == NULL) return NULL;

    if(watch_obj) {
        lv_phonebook_obj_ext_t * ext = lv_obj_get_ext_attr(watch_obj);
        return ext;
    }
    return NULL;
}

static void phonebook_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_png_cache_all_free();

    if(1 == phonebook_get_user_num(activity_obj)) {
        lv_phonebook_obj_ext_t * ext = phonebook_get_ext();
        if(ext == NULL) return;
        _lv_ll_clear(&ext->contact_list);
    }
}

static int aligned_width(int val, lv_coord_t move_width)
{
    bool negative = (val < 0) ? true : false;
    if(val < 0)  val = -val;
    int i = (val + move_width - 1) / move_width - 1;
    int new_val = i * move_width;

    new_val = (val - new_val > move_width / 2) ? (new_val + move_width) : new_val;
    new_val = negative ? -new_val : new_val;
    return new_val;
}

/*If drag throw is enabled(defaultly is enabled by lv_page's page scrl), when LV_SIGNAL_RELEASED, indev drag still has acceleration,
 *After LV_SIGNAL_DRAG_END, page is moved to position which add acceleration.
 *If drag throw is disabled, signal LV_SIGNAL_DRAG_END same as LV_SIGNAL_RELEASED, indev drag still has acceleration
 */
static lv_res_t page_scrl_signal_handler(lv_obj_t * page_scrl, lv_signal_t sign, void * param)
{
    lv_phonebook_obj_ext_t * ext = phonebook_get_ext();
    LV_ASSERT_MEM(ext);
    if(ext == NULL) return LV_RES_INV;

    if(sign == LV_SIGNAL_PRESSED) {
        ext->point_start.x = lv_obj_get_x(page_scrl);
    } else if((sign == LV_SIGNAL_DRAG_END) || (sign == LV_SIGNAL_RELEASED)) {
        lv_coord_t new_x = lv_obj_get_x(page_scrl);
        lv_coord_t aligned_new_x = aligned_width(new_x, ext->move_width);
        lv_obj_set_x(page_scrl, aligned_new_x);

        /* update image id */
        ext->cur_img_id = (aligned_new_x < 0) ? -aligned_new_x / ext->move_width : aligned_new_x / ext->move_width;
        phonebook_t * contact = _lv_ll_get_head(&ext->contact_list);
        uint8_t i = 0;
        while(NULL != contact) {
            if(ext->cur_img_id == i) {
                ext->cur_contact = contact;
                break;
            }
            i++;
            contact = _lv_ll_get_next(&ext->contact_list, contact);
        }
        if(NULL == contact) {
            printf("error, contact not found in page_scrl_signal_handler\n");
            return LV_RES_INV;
        }
        lv_label_set_text(ext->label_phone_name, ext->cur_contact->name);
        lv_label_set_text(ext->label_phone_number, ext->cur_contact->number);
    }

    return LV_RES_OK;
}

static lv_res_t phonebook_page_scrl_signal(lv_obj_t * page_scrl, lv_signal_t sign, void * param)
{
    lv_res_t res;

    /* Include the ancient signal function */
    res = ancient_page_scrl_signal(page_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    if(sign == LV_SIGNAL_CLEANUP) {
        /*when cleanup, can not invoke lv_indev_get_act */
        return LV_RES_OK;
    }

    res = page_scrl_signal_handler(page_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    return LV_RES_OK;
}

static lv_res_t element_signal_func(lv_obj_t * element, lv_signal_t sign, void * param)
{
    lv_res_t res;

    /* if not click signs, invoke ancient handler and return */
    if(sign != LV_SIGNAL_PRESSING && sign != LV_SIGNAL_PRESSED && sign != LV_SIGNAL_RELEASED && sign != LV_SIGNAL_PRESS_LOST) {
        /* Include the ancient signal function */
        lv_signal_cb_t sign_func = (lv_signal_cb_t)(element->user_data.user_data);
        res = sign_func(element, sign, param);
        if(res != LV_RES_OK) return res;
        return LV_RES_OK;
    }

    lv_obj_t * obj = lv_obj_get_parent(element);
    while(obj) {
        if(lv_obj_get_signal_cb(obj) != lv_watch_obj_signal) obj = lv_obj_get_parent(obj);
        else break;
    }
    if(obj == NULL) return LV_RES_INV;

    res = lv_watch_obj_signal(obj, sign, param);
    if(res != LV_RES_OK) return res;

    lv_phonebook_obj_ext_t * ext = lv_obj_get_ext_attr(obj);
    if(ext == NULL) return LV_RES_INV;
    if(ext->page_scrl == NULL) return LV_RES_INV;
    res = page_scrl_signal_handler(ext->page_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    /* Include the ancient signal function */
    lv_signal_cb_t sign_func = (lv_signal_cb_t)(element->user_data.user_data);
    res = sign_func(element, sign, param);
    if(res != LV_RES_OK) return res;

    return LV_RES_OK;
}

static void phonebook_add_element(lv_obj_t * element)
{
    void * user_data = NULL;
    user_data = (void *)lv_obj_get_signal_cb(element);
    lv_watch_set_free_ptr(element, user_data);
    lv_obj_set_signal_cb(element, element_signal_func);
    lv_obj_add_protect(element, LV_PROTECT_PRESS_LOST | LV_PROTECT_CLICK_FOCUS);
    return;
}

static lv_obj_t * call_number_add(lv_obj_t * page, const void * img_src)
{
    lv_obj_t * img = NULL;

    lv_obj_t * content = lv_cont_create(page, NULL);
    lv_obj_add_style(content, LV_OBJ_PART_MAIN, &lv_watch_style_transp);
    lv_obj_set_size(content,  120, lv_obj_get_height(page));
    lv_cont_set_layout(content, LV_LAYOUT_OFF);
    LV_ASSERT_MEM(content);
    if(content == NULL) return NULL;
    /*must set click enable for these content, If not, no pressed/pressing signals out*/
    lv_obj_set_click(content, true);
    phonebook_add_element(content);
    /*hor drag in page, must set glue*/
    lv_page_glue_obj(content, true);

    img = lv_img_create(content, NULL);
    lv_img_set_src(img, img_src);
    lv_obj_align(img, content, LV_ALIGN_IN_TOP_MID, 0, 20);

    return content;
}

static lv_obj_t * append_obj(lv_obj_t * page)
{
    lv_phonebook_obj_ext_t * ext = phonebook_get_ext();

    lv_obj_t * append_obj = lv_obj_create(page, NULL);
    lv_obj_set_size(append_obj, ext->append_width, lv_obj_get_height(page));
    lv_obj_add_style(append_obj, LV_OBJ_PART_MAIN, &lv_style_transp_tight);
    phonebook_add_element(append_obj);
    return append_obj;
}

static lv_obj_t * phonebook_add(lv_obj_t * page)
{
    lv_phonebook_obj_ext_t * ext = phonebook_get_ext();
    phonebook_t * contact = _lv_ll_get_head(&ext->contact_list);
    const char * img_src;

    img_src = phonebook_get_img_src(contact->portrait_id, 0);

    lv_img_header_t * img_header = (lv_img_header_t *)lv_mem_alloc(sizeof(lv_img_header_t));

    memset(img_header, 0, sizeof(lv_img_header_t));
    lv_img_decoder_get_info(img_src, img_header);
    ext->append_width = (lv_obj_get_width(page) - img_header->w - 2 * ext->body_padding_inner) / 2;
    append_obj(page);

    while(NULL != contact) {
        img_src = phonebook_get_img_src(contact->portrait_id, 0);
        call_number_add(page, (void *)img_src);
        contact = _lv_ll_get_next(&ext->contact_list, contact);
    }

    append_obj(page);

    return page;
}

static void phonebook_btn_event_cb(lv_obj_t * btn, lv_event_t e)
{
    lv_phonebook_obj_ext_t * ext = NULL;
    uint8_t length = 0;
    char * number = NULL;
    if(LV_EVENT_CLICKED == e) {
        ext = phonebook_get_ext();
        length = strlen(ext->cur_contact->number) + 1;
        number = (char *)lv_mem_alloc(length);
        memcpy(number, ext->cur_contact->number, length);
        phone_voice_call_req(number);
    }
}

static void phonebook_add_btn(lv_obj_t * obj, char * phone_name, char * phone_number, lv_event_cb_t event_cb)
{
    lv_obj_t * content = lv_cont_create(obj, NULL);
    LV_ASSERT_MEM(content);
    if(content == NULL) return;
    lv_obj_add_style(content, LV_OBJ_PART_MAIN, &lv_watch_style_transp);
    lv_cont_set_fit(content, LV_FIT_TIGHT);
    lv_cont_set_layout(content, LV_LAYOUT_COLUMN_MID);
    lv_obj_set_click(content, false);
    lv_obj_align(content, obj, LV_ALIGN_CENTER, 0, 3);
    lv_obj_set_style_local_pad_inner(content, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);

    lv_obj_t * btn;
    btn = lv_imgbtn_create(content, NULL);
    lv_imgbtn_set_src(btn, LV_BTN_STATE_RELEASED, ICON_CALL_PNG);
    lv_imgbtn_set_src(btn, LV_BTN_STATE_PRESSED, ICON_CALL_PNG);
    lv_obj_set_event_cb(btn, event_cb);
    phonebook_add_element(btn);

    lv_phonebook_obj_ext_t * ext = phonebook_get_ext();;
    lv_obj_t * label = lv_label_create(content, NULL);
    lv_obj_add_style(label, LV_OBJ_PART_MAIN, &lv_watch_font20);
    lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
    lv_label_set_text(label, phone_name);
    lv_obj_set_click(label, false);
    ext->label_phone_name = label;

    label = lv_label_create(content, NULL);
    lv_obj_add_style(label, LV_OBJ_PART_MAIN, &lv_watch_font20);
    lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
    lv_label_set_text(label, phone_number);
    lv_obj_set_click(label, false);
    ext->label_phone_number = label;

    lv_obj_add_style(content, LV_OBJ_PART_MAIN, &lv_style_transp_fit);

    return;
}

static void phonebook_note_create(lv_obj_t * obj, uint16_t text_id)
{
    lv_obj_t * label = lv_label_create(obj, NULL);
    lv_obj_add_style(label, LV_OBJ_PART_MAIN, &lv_watch_font20);

    lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
    lv_obj_set_click(label, false);
    lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK);
    lv_obj_set_width(label, lv_obj_get_width(obj) - 50);
    lv_label_set_text_id(label, text_id);
    lv_obj_align(label, obj, LV_ALIGN_CENTER, 0, 0);

    return;
}

static void phonebook_add_contact(lv_obj_t * obj)
{
    lv_phonebook_obj_ext_t * ext = lv_obj_get_ext_attr(obj);

    lv_obj_t * page = lv_page_create(obj, NULL);
    LV_ASSERT_MEM(page);
    if(page == NULL) return;
    lv_obj_set_size(page, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_page_set_scrollbar_mode(page, LV_SCROLLBAR_MODE_OFF);
    lv_page_set_edge_flash(page, true);

    lv_obj_add_style(page, LV_PAGE_PART_BG, &lv_style_transp_tight);
    lv_obj_add_style(page, LV_PAGE_PART_SCROLLABLE, &lv_style_transp_tight);
    lv_obj_add_style(page, LV_PAGE_PART_SCROLLBAR, &lv_style_transp_tight);
    lv_page_set_scrollable_fit(page, LV_FIT_TIGHT);

    ext->page_scrl = lv_page_get_scrollable(page);
    ancient_page_scrl_signal = lv_obj_get_signal_cb(ext->page_scrl);
    lv_obj_set_signal_cb(ext->page_scrl, phonebook_page_scrl_signal);
    /*the lv_page disable use acceleration, so sinal LV_SIGNAL_DRAG_END is same as LV_SIGNAL_RELEASED */
    lv_obj_set_drag_throw(ext->page_scrl, false);

    lv_page_set_scrl_layout(page, LV_LAYOUT_ROW_MID);
    phonebook_add(page);
    phonebook_add_element(ext->page_scrl);
    lv_obj_set_x(ext->page_scrl, ((-1) * ext->move_width * ext->cur_img_id));

    phonebook_add_btn(obj, ext->cur_contact->name, ext->cur_contact->number, phonebook_btn_event_cb);
}

#if PHONEBOOK_USE_RIGHT_SLIDE_HIDE != 0
static void phonebook_right_press_lost(lv_obj_t  * obj)
{
    lv_phonebook_obj_ext_t * ext = phonebook_get_ext();
    LV_ASSERT_MEM(ext);
    if(ext == NULL) return;

    lv_coord_t x = lv_obj_get_x(ext->page_scrl);

    if(ext->point_start.x == 0 && x == 0) {
        lv_watch_obj_hor_hide_action(lv_watch_get_activity_obj(ACT_ID_PHONEBOOK), LV_HOR_RES);
    }
}
#endif

static void phonebook_contacts_create(lv_obj_t * obj, lv_ll_t * contact_list)
{
    /*when pressing, y is not updated. When press lost  */
#if PHONEBOOK_USE_RIGHT_SLIDE_HIDE != 0
    lv_watch_obj_set_anim_mode(obj, LV_WATCH_LEFT_OR_RIGHT_SLIDE_CALLBACK);
    lv_watch_obj_set_right_slide_cb(obj, phonebook_right_press_lost);
#else
    lv_watch_obj_set_anim_mode(obj, LV_WATCH_ANIM_VER_UP_HIDE);
#endif
    lv_watch_obj_ext_t * watch_obj_ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_watch_obj_ext_t));
    if(watch_obj_ext == NULL) return;

    /*Allocate the object type specific extended data*/
    lv_phonebook_obj_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_phonebook_obj_ext_t));
    /*ext realloc based on watch_obj_ext, do not memeset it*/
    LV_ASSERT_MEM(ext);
    if(ext == NULL) return;
    ext->contact_list.n_size = contact_list->n_size;
    ext->contact_list.head = contact_list->head;
    ext->contact_list.tail = contact_list->tail;
    ext->body_padding_inner = 1;
    ext->append_width = 50;
    ext->cur_img_id = 0;
    ext->cur_contact = _lv_ll_get_head(&ext->contact_list);
    ext->label_phone_name = NULL;
    ext->label_phone_number = NULL;
    ext->move_width = ext->body_padding_inner + ext->append_width + (lv_obj_get_width(obj) / 2 - ext->append_width); //130;
    ext->point_start.x = 0;
    ext->point_start.y = 0;

    phonebook_add_contact(obj);
}

#if PHONEBOOK_TEST != 0
static void phonebook_write_nvm_for_test(void)
{
    /*clear nvm*/
    uint32_t length = sizeof(nv_watch_phonebook_t);
    nv_watch_phonebook_t * nvm_clear = (nv_watch_phonebook_t *)lv_mem_alloc(length);
    memset(nvm_clear, 0, length);
    phonebook_write_nvm(nvm_clear);
    lv_mem_free(nvm_clear);

    nv_watch_phonebook_t nvm_list = {{
            {"admin", "1", 0, WATCH_PORTRAIT_ID_MOTHER},
            {"standby admin 1", "11", 1, WATCH_PORTRAIT_ID_FATHER},
            {"standby admin 2", "22", 1, WATCH_PORTRAIT_ID_GIRL},
            {"others 1", "111", 2, WATCH_PORTRAIT_ID_UNCLE},
            {"others 2", "222", 2, WATCH_PORTRAIT_ID_AUNT},
            {"others 3", "333", 2, WATCH_PORTRAIT_ID_PATERNAL_GRADFATHER},
            {"others 4", "444", 2, WATCH_PORTRAIT_ID_MATERNAL_GRADMOTHER},
            {"others 5", "555", 2, WATCH_PORTRAIT_ID_BOY},
            {"others 6", "666", 2, WATCH_PORTRAIT_ID_OTHERS},
        }
    };
    length = sizeof(nvm_list);
    if(length != UI_NV_Write_Req(NV_SECTION_UI_PHONEBOOK, 0, length, (uint8_t *)(&nvm_list))) {
        printf("write nvm error in phonebook_write_nvm_for_test\n");
    }
}
#endif

static void phonebook_update_ui(app_adaptor_contact_t * contact_list, uint8_t count, uint8_t standby_admin_count, uint8_t others_count)
{
    if(NULL == contact_list) {
        printf("error, contact_list NULL in phonebook_update_ui\n");
        return;
    }

    lv_obj_t * activity_obj = lv_watch_get_activity_obj(ACT_ID_PHONEBOOK);
    if(NULL == activity_obj) return;
    lv_obj_t * obj;
    lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
    if(obj == NULL) return;

    uint8_t type = contact_list[0].contact_type; /*0:administrator, 1:standby administrator, 2: others*/
    if(0 == phonebook_get_user_num(activity_obj)) {
        /*no contacts or network*/
        if((1 == count)
                && (0 == type)
                && (MMI_MODEM_VOICE_EMERGENCY != watch_modem_voice_registration_state_req())) {
            /*add a new administrator*/
            lv_obj_clean(obj);

            lv_ll_t list;
            _lv_ll_init(&list, sizeof(phonebook_t));
            phonebook_t * contact = _lv_ll_ins_tail(&list);
            phonebook_copy_contact(contact, &contact_list[0]);
            phonebook_contacts_create(obj, &list);
            lv_watch_set_free_num(activity_obj, 1);
        } else {
            printf("not update phonebook ui in phonebook_update_ui\n");
        }
    } else {
        lv_phonebook_obj_ext_t * ext = lv_obj_get_ext_attr(obj);
        if(NULL == ext) return;

        if(0 == type) {
            if(0 == count) {
                /*administrator deleted, no contacts exists*/
                lv_obj_clean(obj);

                phonebook_note_create(obj, WATCH_TEXT_ID_NO_CONTACTS_OR_NETWORK);
                lv_watch_set_free_num(activity_obj, 0);
            } else {
                printf("error, admin has been present in phonebook_update_ui\n");
            }
            return;
        }

        /*update standby administrator or others*/
        uint8_t old_count = standby_admin_count; /*for type 1*/
        if(2 == type) {
            old_count = others_count;
        }

        phonebook_t * contact = _lv_ll_get_head(&ext->contact_list);
        if(count > old_count) {
            /*add*/
            if(1 == type) {
                if(0 == others_count) {
                    contact = _lv_ll_ins_tail(&ext->contact_list);
                } else {
                    while(NULL != contact) {
                        if(type < contact->contact_type) {
                            contact = _lv_ll_ins_prev(&ext->contact_list, contact);
                            break;
                        }

                        contact = _lv_ll_get_next(&ext->contact_list, contact);
                    }
                    if(NULL == contact) {
                        printf("error, no others in app_adaptor_update_phonebook_ind\n");
                        return;
                    }
                }

                ext->cur_contact = contact;
                ext->cur_img_id = 1 + standby_admin_count;
            } else if(2 == type) {
                contact = _lv_ll_ins_tail(&ext->contact_list);
            }

            phonebook_copy_contact(contact, &contact_list[old_count]);
        } else if(count == old_count) {
            /*modify*/
            uint8_t i = 0;
            while(NULL != contact) {
                if(type == contact->contact_type) {
                    if((0 != strcmp(contact->name, contact_list[i].contact_name))
                            || (0 != strcmp(contact->number, contact_list[i].contact_number))) {
                        phonebook_copy_contact(contact, &contact_list[i]);
                        break;
                    }
                    i++;
                    if(count == i) {
                        printf("error, the modifed contact not found in app_adaptor_update_phonebook_ind\n");
                        return;
                    }
                }

                contact = _lv_ll_get_next(&ext->contact_list, contact);
            }
        } else {
            /*delete*/
            bool cur_flag = false;
            uint8_t i = 0;
            while(NULL != contact) {
                if(type == contact->contact_type) {
                    if(count <= i) {
                        /*delete the last contact*/
                        break;
                    }

                    if((0 != strcmp(contact->name, contact_list[i].contact_name))
                            || (0 != strcmp(contact->number, contact_list[i].contact_number))) {
                        break;
                    }
                    i++;
                } else if(type < contact->contact_type) {
                    printf("error, the deleted contact not found in app_adaptor_update_phonebook_ind\n");
                    return;
                }

                if(ext->cur_contact == contact) {
                    cur_flag = true;
                }

                contact = _lv_ll_get_next(&ext->contact_list, contact);
            }
            if(NULL == contact) {
                printf("error, the deleted contact not found in app_adaptor_update_phonebook_ind\n");
                return;
            }
            if(ext->cur_contact == contact) {
                ext->cur_contact = _lv_ll_get_next(&ext->contact_list, contact);
                if(NULL == ext->cur_contact) {
                    ext->cur_contact = _lv_ll_get_prev(&ext->contact_list, contact);
                    ext->cur_img_id--;
                }
            } else if(false == cur_flag) {
                /*the deleted contact is behind current contact*/
                ext->cur_img_id--;
            }
            _lv_ll_remove(&ext->contact_list, contact);
            lv_mem_free(contact);
        }

        lv_obj_clean(obj);

        phonebook_add_contact(obj);
    }
}

static lv_obj_t * phonebook_create(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

#if PHONEBOOK_TEST != 0
    /*clear nvm and write contacts into nvm*/
    phonebook_write_nvm_for_test();
#endif

    if(activity_obj == NULL) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_PHONEBOOK;
        activity_ext.create = phonebook_create;
        activity_ext.prepare_destory = phonebook_prepare_destory;
        activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);
        if(activity_obj == NULL) return NULL;
    }

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    if(obj == NULL) return NULL;

    if(setting_is_flying_mode()) {
        phonebook_note_create(obj, WATCH_TEXT_ID_PLEASE_TURN_OFF_FLYING_MODE);
        lv_watch_set_free_num(activity_obj, 0);
        return obj;
    }

    nv_watch_phonebook_t * nvm_contact_list = phonebook_read_nvm();

#if PHONEBOOK_TEST != 0
    if('\0' == nvm_contact_list->info[0].number[0]) {
#else
    if(('\0' == nvm_contact_list->info[0].number[0])
            || (false == watch_modem_sim_present_check_req())
            || (MMI_MODEM_SIGNAL_BAR_0 == watch_modem_get_signal_bar_req())) {
#endif
        /*no contacts or network*/
        phonebook_note_create(obj, WATCH_TEXT_ID_NO_CONTACTS_OR_NETWORK);
        lv_watch_set_free_num(activity_obj, 0);
    } else {
        lv_ll_t contact_list;
        _lv_ll_init(&contact_list, sizeof(phonebook_t));
        phonebook_t * contact;
        uint32_t contact_length = sizeof(nv_watch_phonebook_info_t);
        for(uint8_t i = 0; i < NV_WATCH_MAX_CONTACT_NUM; i++) {
            if('\0' == nvm_contact_list->info[i].number[0]) {
                break;
            }
            contact = _lv_ll_ins_tail(&contact_list);
            memcpy(contact, &nvm_contact_list->info[i], contact_length);
        }
        phonebook_contacts_create(obj, &contact_list);
        lv_watch_set_free_num(activity_obj, 1);
    }

    lv_mem_free(nvm_contact_list);

    return obj;
}

#endif /*USE_LV_WATCH_PHONEBOOK*/
